package TestSuite;

import java.util.Random;
import java.util.Stack;

import bosVM2.Engine.ExecutionEngine;
import bosVM2.Engine.Frames.MethodFrame;
import bosVM2.meta.Objects.BosClass;

public class Injector{ 
	
	Random rnd = new Random(2);	//Random seeded value used for random selection of fault types 
	
	//function to print integers as binary strings, used primarily for debugging
	public String binaryString(int b)
	{
		String bString = "";
		int temp = 0;
		
		for(int i = 7; i >= 0; -- i)
		{
			temp = (int) (b % Math.pow(2, i));	
			
			if(temp == b)
			{
				bString = bString + "0";
			}
			else
			{
				bString = bString + "1";
			}
			b = temp;
		}
		return bString;
	}
	
	//flips a bit at a position provided by the programmer
	public int flip (int input, int position)
	{
		int positionbyte = 1;
		
		//integer that is used to flip in the right position by leftshifting a bit into position 
		positionbyte = positionbyte << position;
		
		//XOR the input and positionbyte will generate an integer that is as the original but flipped the bit at position
		input = input^positionbyte;
		return input;
	}
	
	
	//flips a random bit 
	public int flip (int input)
	{
		int positionbyte = 1;
		positionbyte = positionbyte << rnd.nextInt(8);
		input = input^positionbyte;
		return input;
		
	}
	
	//Takes a string as argument and flips a random bit in a random char of that string
	public String stringHandler(String s)
	{
		char[] a = s.toCharArray();
		int index = rnd.nextInt(a.length);
		a[index] = (char)flip((int)a[index]);
		s = String.valueOf(a); 
		return s;
	}
	
	//injects a fault in the program counter
	public void pcInjection(int[] i)
	{
		int index = rnd.nextInt(2);
		i[index] = flip(i[index]);
	}
	
	//injects a fault in a value (can be either original or AN-encoded value)
	public void valueInjection(Stack<Object> s)
	{
		int index = rnd.nextInt(s.size());
		int i = flip((int)s.get(index));
		s.set(index, i);
	}
	
	//injects a fault in the opcode
	public Object opcodeInjection(Object o[])
	{
		o[0] = flip((int)o[0]);
		return o[0];
	}
	
	//injects a fault in the arit opcode
	public Object aritInjection(Object o[])
	{
		o[1] = flip((int)o[1]);
		return o[1];
	}
	
	//injects a fault in the if opcode
	public Object ifInjection(Object o[])
	{
		o[1] = flip((int)o[1]);
		return o[1];
	}
	
	//injects a fault in the name of a class from the meta data
	public void metaDataClassInjection(ExecutionEngine e)
	{
		int i = rnd.nextInt(e.getMeta().getClasses().size());
		int j = 0;
		for(String s: e.getMeta().getClasses())
		{
			if(i == j)
			{
				//System.out.println("Classname before: " + e.getMeta().getMetaClass(s).getClassName());
				/*int u = rnd.nextInt(2);
				switch(u)
				{
					case 0: e.getMeta().getMetaClass(s).setClassName(stringHandler(e.getMeta().getMetaClass(s).getClassName())); break;
				
					case 1: e.getMeta().getMetaClass(s).setClassName(stringHandler(e.getMeta().getMetaClass(s).getClassName())); break;
				 
					case 2: e.getMeta().getMetaClass(s).setClassName(stringHandler(e.getMeta().getMetaClass(s).getClassName())); break;
				}*/
				
				e.getMeta().getMetaClass(s).setClassNameI(stringHandler(e.getMeta().getMetaClass(s).getClassNameI()));
				
				//System.out.println("Classname after: " + e.getMeta().getMetaClass(s).getClassName());
				
			}
			++j;
		}
	}
	
	//injects a fault in the name of a method from the meta data
	public void metaDataMethodInjection(ExecutionEngine e)
	{
		int i = rnd.nextInt(e.getMeta().getClasses().size());
		int j = 0;
		for(String t: e.getMeta().getClasses())
		{
			//System.out.println("i = " + i + " and j = " + j);
			if(i == j)
			{
				//System.out.println("the class is " + t);
				int o = rnd.nextInt(e.getMeta().getMetaClass(t).getMethods().size());
				int k = 0;
				for(String h: e.getMeta().getMetaClass(t).getMethods())
				{
					//System.out.println("o = " + o + " and k = " + k);
					if(o == k)
					{
						//System.out.println("the method is " + h);
						int g = rnd.nextInt(4);
						//System.out.println("the case switcher is " + g);
						switch(g)
						{
							case 3: //System.out.println("numArgs origninal before: " + e.getMeta().getMetaClass(t).getMethod(h).getNumArg(0));
									e.getMeta().getMetaClass(t).getMethod(h).setNumArg(flip(e.getMeta().getMetaClass(t).getMethod(h).getNumArg(0)),0); 
									//System.out.println("numArgs original after: " + e.getMeta().getMetaClass(t).getMethod(h).getNumArg(0));
									//System.out.println("Class that method is from: " + e.getMeta().getMetaClass(t).getMethod(h).getSuper().getClassNameI());
									break;
							
							
							case 1: //System.out.println("numArgs AN before: " + e.getMeta().getMetaClass(t).getMethod(h).getNumArg(1));
									e.getMeta().getMetaClass(t).getMethod(h).setNumArg(flip(e.getMeta().getMetaClass(t).getMethod(h).getNumArg(1)),1); 
									//System.out.println("numArgs AN after: " + e.getMeta().getMetaClass(t).getMethod(h).getNumArg(1));
									//System.out.println("Class that method is from: " + e.getMeta().getMetaClass(t).getMethod(h).getSuper().getClassNameI());
									break;
							
							
							case 0: //System.out.println("Methodname before: " + e.getMeta().getMetaClass(t).getMethod(h).getNameI());
									e.getMeta().getMetaClass(t).getMethod(h).setNameI(stringHandler(e.getMeta().getMetaClass(t).getMethod(h).getNameI())); 
									//System.out.println("Methodname after: " + e.getMeta().getMetaClass(t).getMethod(h).getNameI());
									//System.out.println("Class that method is from: " + e.getMeta().getMetaClass(t).getMethod(h).getSuper().getClassNameI());
									break;
									
							case 2: if(e.getMeta().getMetaClass(t).getMethod(h).getArgs().length > 0)
									{
										int index = rnd.nextInt(e.getMeta().getMetaClass(t).getMethod(h).getArgs().length);
										e.getMeta().getMetaClass(t).getMethod(h).setArg(index, stringHandler(e.getMeta().getMetaClass(t).getMethod(h).getArgs()[index]));
										String tt = stringHandler(e.getMeta().getMetaClass(t).getMethod(h).getArgsCoded()[index]);
										e.getMeta().getMetaClass(t).getMethod(h).setArg(index, tt);
										break;
									}
						}
					}
					++k;
				}
			}
			++j;
		}
	}
	
	public void metaDataFieldInjection(ExecutionEngine e)
	{
		int i = rnd.nextInt(e.getMeta().getClasses().size());
		int j = 0;
		for(String t: e.getMeta().getClasses())
		{
			if(i == j)
			{
				int o = rnd.nextInt(e.getMeta().getMetaClass(t).getMethods().size());
				int k = 0;
				for(String h: e.getMeta().getMetaClass(t).getFields())
				{
					if(o == k)
					{
						//System.out.println("Fieldname before: " + e.getMeta().getMetaClass(t).getField(h).getNameI());
						e.getMeta().getMetaClass(t).getField(h).setNameI(stringHandler(e.getMeta().getMetaClass(t).getField(h).getNameI()));
						//System.out.println("Fieldname after: " + e.getMeta().getMetaClass(t).getField(h).getNameI());
						//System.out.println("Class that Field is from: " + e.getMeta().getMetaClass(t).getField(h).getBosClass().getClassNameI());
					}
					++k;
				}
			}
			++j;
		}
	}
	
	public void metaDataKeyInjection(ExecutionEngine e)
	{
		int i = rnd.nextInt(e.getMeta().getClasses().size());
		int j = 0;
		String t = "";
		for(String s: e.getMeta().getClassesCoded())
		{
			if(i == j)
			{
				t = s;
			}
			++ j;
		}
		//System.out.println("This is t:  " +t);
		//System.out.println("gamle keys er: " + e.getMeta().getClasses());
		BosClass b = e.getMeta().getClassMap().remove(t);
		
		t = stringHandler(t);
		e.getMeta().getClassMap().put(t, b);
		//System.out.println("nye keys er: " + e.getMeta().getClasses());
		
	}
	
	//Method that chooses a random fault type and injects it.
	public void inject(MethodFrame m)
	{
		//randomly choose
		int i = rnd.nextInt(9);
		//System.out.println("Injection is " + i + " !!!!!!!");
		
		switch(i)
		{
			//Opcode injection
			case 0: opcodeInjection(m.getMethod().instructionAt(m.getpc())); break;				
			
			//Arit opcode injection
			case 1: if((int)m.getMethod().instructionAt(m.getpc())[0] == 14)					
					{
						aritInjection(m.getMethod().instructionAt(m.getpc()));
					}
					else
					{
						opcodeInjection(m.getMethod().instructionAt(m.getpc()));
					}
					break;
			
			//If opcode injection		
			case 2: if((int)m.getMethod().instructionAt(m.getpc())[0] == 13)							
					{
						ifInjection(m.getMethod().instructionAt(m.getpc()));
					}
					else
					{
						opcodeInjection(m.getMethod().instructionAt(m.getpc()));
					}
					break;
			
			//Value injection
			case 3: if(!m.getOs().empty())
					{
						valueInjection(m.getOs());
					}
					else
					{
						opcodeInjection(m.getMethod().instructionAt(m.getpc()));
					}
					break;
			
			//Program counter injection
			case 4: pcInjection(m.getpcArray()); break;
			
			//Metadata class injection
			case 5:	metaDataClassInjection(m.getEngine()); break;									
			
			//Metadata method injection
			case 6: metaDataMethodInjection(m.getEngine());	break;		
			
			//Metadata field injection
			case 7: metaDataFieldInjection(m.getEngine()); break;
			
			case 8: metaDataKeyInjection(m.getEngine()); break;
			
		}
	}
}
